/**
 * 
 */
package android.springframework;

import static org.ow2.asmdex.Opcodes.*;

import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.ow2.asmdex.AnnotationVisitor;
import org.ow2.asmdex.ApplicationReader;
import org.ow2.asmdex.ApplicationVisitor;
import org.ow2.asmdex.ClassVisitor;
import org.ow2.asmdex.FieldVisitor;

import android.content.Context;
import android.util.Log;

/**
 * 基于注解的BeanContext
 * 
 */
public class AnnotaionBeanContext implements BeanContext {
	/**
	 * 类扫描的基础包
	 */
	private final String basePkgToScan;
	private HashMap<String, Object> namedBeans = new HashMap<String, Object>();
	private HashMap<Class, Object> classBeans = new HashMap<Class, Object>();
	private HashMap<String, Class> descToClass = new HashMap<String, Class>();
	/**
	 * Android Application Context - 供所有使用Context的非Android组件类公共使用
	 */
	private final Context context;

	private static final String COMPONNET_ANNO_DESC = "Landroid/springframework/Component;";
	private static final String RESOURCE_ANNO_DESC = "Landroid/springframework/Resource;";
	private static final int API_ASM = ASM4;

	public AnnotaionBeanContext(String basePkgToScan, Context context) {
		Log.d(tag, "AnnotaionBeanContext 初始化 ");

		this.basePkgToScan = basePkgToScan;
		this.context = context;

		try {
			initApplication();
		} catch (Exception e) {
			Log.e(tag, Log.getStackTraceString(e));
		}
	}

	private void initApplication() throws Exception {
		URL moduleResUrl = AnnotaionBeanContext.class.getClassLoader()
				.getResource("AndroidManifest.xml");
		String path = moduleResUrl.getFile();
		String apkPath = path.substring(path.indexOf('/'), path.indexOf("!/"));
		Log.d(tag, "apkPath = " + apkPath);

		ZipFile zipFile = new ZipFile(apkPath);
		ZipEntry classesEntry = zipFile.getEntry("classes.dex");
		InputStream classesStream = zipFile.getInputStream(classesEntry);
		ApplicationReader ar = new ApplicationReader(API_ASM, classesStream);
		AppVisitor appVistor = new AppVisitor();
		appVistor.setModulePackage(basePkgToScan);
		ar.accept(appVistor, 0);
		classesStream.close();
		zipFile.close();
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> T getBean(Class<T> clazz) {
		return (T) classBeans.get(clazz);
	}

	@Override
	public <T> T getBean(String name, Class<T> clazz) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Object getBean(String name) {
		// TODO Auto-generated method stub
		return null;
	}

	class AppVisitor extends ApplicationVisitor {
		private String modulePackage;
		private BeanClassVisitor beanClassVisitor = new BeanClassVisitor(api);

		public AppVisitor() {
			super(API_ASM);
		}

		public void setModulePackage(String modulePackage) {
			if (modulePackage.startsWith("L")) {
				this.modulePackage = modulePackage;
			} else {
				this.modulePackage = 'L' + modulePackage.replace('.', '/');
			}
		}

		@Override
		public ClassVisitor visitClass(int access, String name,
				String[] signature, String superName, String[] interfaces) {
			if (name.startsWith(modulePackage)) {
				return beanClassVisitor;
			}
			return null;
		}

	}

	class BeanClassVisitor extends ClassVisitor {

		public BeanClassVisitor(int api) {
			super(api);
		}

		String className;
		BeanFieldVistor beanFieldVistor = new BeanFieldVistor();
		boolean isComponent;
		Object bean;

		@Override
		public void visit(int version, int access, String name,
				String[] signature, String superName, String[] interfaces) {
			Log.i(tag, "*** className = " + name);
			className = name;
			beanFieldVistor.reset();
		}

		@Override
		public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
			Log.i(tag, "@Annotation : desc = " + desc + ", visible = "
					+ visible);
			if (COMPONNET_ANNO_DESC.equals(desc)) {
				isComponent = true;
				bean = initBean(className);
				// for (Field f : bean.getClass().getDeclaredFields()) {
				// Resource resAnn = f.getAnnotation(Resource.class);
				// if (resAnn == null) {
				// continue;
				// }
				// f.setAccessible(true);
				// Object fval = initBean(f.getType());
				// if (fval != null) {
				// try {
				// f.set(bean, fval);
				// } catch (Exception e) {
				// e.printStackTrace();
				// }
				// }
				// }
			}
			return null;
		}

		@Override
		public FieldVisitor visitField(int access, String name, String desc,
				String[] signature, Object value) {
			if (isComponent) {
				beanFieldVistor.set(bean, access, name, desc, signature);
				return beanFieldVistor;
			}
			return null;
		}

	}

	class BeanFieldVistor extends FieldVisitor {

		public BeanFieldVistor() {
			super(API_ASM);
		}

		void reset() {
			access = 0;
			fieldName = "";
			fieldDesc = "";
			signature = null;
			ownerBean = null;
		}

		int access;
		String fieldName;
		String fieldDesc;
		String[] signature;
		Object ownerBean;

		void set(Object bean, int access, String name, String desc,
				String[] signature) {
			this.ownerBean = bean;
			this.access = access;
			this.fieldName = name;
			this.fieldDesc = desc;
			this.signature = signature;
		}

		@Override
		public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
			if (RESOURCE_ANNO_DESC.equals(desc)) {
				try {
					Field field = ownerBean.getClass().getDeclaredField(
							fieldName);
					field.setAccessible(true);
					Object fval = initBean(field.getType());
					if (fval != null) {
						field.set(ownerBean, fval);
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			return super.visitAnnotation(desc, visible);
		}

	}

	private Object initBean(String className) {
		if (className.endsWith(";")) {
			className = className.substring(1, className.length() - 1);
		}
		if (className.contains("/")) {
			className = className.replace('/', '.');
		}
		Object ins = null;
		Class<?> clazz = descToClass.get(className);
		if (clazz == null) {
			try {
				return initBean(clazz = Class.forName(className));
			} catch (Exception e) {
				Log.e(tag, Log.getStackTraceString(e));
			}
		} else {
			ins = classBeans.get(clazz);
		}
		return ins;
	}

	private Object initBean(Class<?> clazz) {
		if (clazz == null) {
			return null;
		}
		Object ins = classBeans.get(clazz);
		if (ins != null) {
			return ins;
		}
		descToClass.put(clazz.getName(), clazz);
		try {
			Constructor<?> defCons = clazz.getConstructor();
			ins = defCons.newInstance();
			classBeans.put(clazz, ins);
		} catch (NoSuchMethodException e) {
			try {
				Constructor<?> cons = clazz.getConstructor(Context.class);
				ins = cons.newInstance(context);
				classBeans.put(clazz, ins);
			} catch (Exception e1) {
				Log.e(tag, Log.getStackTraceString(e1));
			}
		} catch (Exception e) {
			Log.e(tag, Log.getStackTraceString(e));
		}
		return ins;
	}
}
